package com.linhngua.code.xml;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Properties;

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;

public class XmlProcessor {
	
	public static XmlProcessor	INSTANCE			= new XmlProcessor();
	
	public static final String	XALAN_INDENT_AMOUNT	= "{http://xml.apache.org/xslt}indent-amount";
	
	public String getXmlStringFromDoc(Document doc) throws Exception {

		ByteArrayOutputStream out = new ByteArrayOutputStream();
		DOMSource in = new DOMSource(doc);
		Transformer transformer = TransformerFactory.newInstance()
				.newTransformer();
		
		// Configure the transformer
		Properties props = new Properties();
		props.put(OutputKeys.METHOD, "xml");
		props.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
		transformer.setOutputProperties(props);
		transformer.transform(in, new StreamResult(out));
		return out.toString();
	}
	
	public String getXmlStringFromDocByXmlSerializer2(Document doc)
			throws Exception {

		StringWriter out = new StringWriter();
		DOMImplementationLS domImplementationLS = (DOMImplementationLS) doc
				.getImplementation();
		LSOutput lsOutput = domImplementationLS.createLSOutput();
		lsOutput.setCharacterStream(out);
		LSSerializer serializer = domImplementationLS.createLSSerializer();
		serializer.getDomConfig().setParameter("xml-declaration", false);
		serializer.write(doc, lsOutput);
		return out.toString();
	}
	
	@SuppressWarnings("deprecation")
	public String getXmlStringFromDocByXmlSerializer(Document doc)
			throws Exception {

		StringWriter out = new StringWriter();
		
		OutputFormat outputFormat = new OutputFormat(doc);
		outputFormat.setOmitXMLDeclaration(true);
		outputFormat.setIndenting(true);
		XMLSerializer serializer = new XMLSerializer(out, outputFormat);
		serializer.serialize(doc);
		return out.toString();
	}
	
	public String getXmlStringFromFile(String fileName) throws Exception {

		StreamSource streamSource = new StreamSource(fileName);
		Transformer transformer = TransformerFactory.newInstance()
				.newTransformer();
		
		// Configure the transformer
		Properties props = new Properties();
		props.put(OutputKeys.METHOD, "xml");
		props.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
		transformer.setOutputProperties(props);
		
		StringWriter stringWriter = new StringWriter();
		
		transformer.transform(streamSource, new StreamResult(stringWriter));
		
		return stringWriter.toString();
	}
	
	public String getXmlStringFromDocWithNewLine(Document doc) throws Exception {

		ByteArrayOutputStream out = new ByteArrayOutputStream();
		DOMSource in = new DOMSource(doc);
		Transformer transformer = TransformerFactory.newInstance()
				.newTransformer();
		// Configure the transformer
		Properties props = new Properties();
		props.put(OutputKeys.METHOD, "xml");
		props.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
		props.put(OutputKeys.INDENT, "yes");
		props.put(XALAN_INDENT_AMOUNT, "2");
		transformer.setOutputProperties(props);
		transformer.transform(in, new StreamResult(out));
		return out.toString();
	}
	
	public Document getXmlDocument(String xmlString) throws Exception {

		DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = fac.newDocumentBuilder();
		ByteArrayInputStream in = new ByteArrayInputStream(xmlString.getBytes());
		Document doc = builder.parse(in);
		return doc;
	}
	
	public Document getXmlDocumentFromFile(String fileName) throws Exception {

		DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = fac.newDocumentBuilder();
		InputStream in = new FileInputStream(fileName);
		Document doc = builder.parse(in);
		return doc;
	}
	
	public Node getNode(String xpathQuery, Document doc) throws Exception {

		XPath xPath = XPathFactory.newInstance().newXPath();
		
		XPathExpression xPathExpression = xPath.compile(xpathQuery);
		return (Node) xPathExpression.evaluate(doc, XPathConstants.NODE);
	}
	
	public Node getNode(String query, Node parentNode) throws Exception {

		XPath xPath = XPathFactory.newInstance().newXPath();
		XPathExpression xPathExpression = xPath.compile(query);
		return (Node) xPathExpression.evaluate(parentNode, XPathConstants.NODE);
		
	}
	
	public NodeList getNodeList(String xpathQuery, Document doc)
			throws Exception {

		XPath xPath = XPathFactory.newInstance().newXPath();
		XPathExpression xPathExpression = xPath.compile(xpathQuery);
		return (NodeList) xPathExpression.evaluate(doc, XPathConstants.NODESET);
	}
	
	public NodeList getNodeList(String xpathQuery, Document doc,NamespaceContext namespaceContext)
			throws Exception {

		XPath xPath = XPathFactory.newInstance().newXPath();
		xPath.setNamespaceContext(namespaceContext);
		XPathExpression xPathExpression = xPath.compile(xpathQuery);
		return (NodeList) xPathExpression.evaluate(doc, XPathConstants.NODESET);
	}
	
	public NodeList getNodeList(String xpathQuery, String xml) throws Exception {

		XPath xPath = XPathFactory.newInstance().newXPath();
		XPathExpression xPathExpression = xPath.compile(xpathQuery);
		return (NodeList) xPathExpression.evaluate(new InputSource(
				new StringReader(xml)), XPathConstants.NODESET);
	}
	
	public NodeList getNodeList(String xpathQuery, Node node) throws Exception {

		XPath xPath = XPathFactory.newInstance().newXPath();
		XPathExpression xPathExpression = xPath.compile(xpathQuery);
		return (NodeList) xPathExpression
				.evaluate(node, XPathConstants.NODESET);
	}
	
	public void injectCDATA(String xpathQuery, Document doc, String cdata)
			throws Exception {

		CDATASection cdataNode = getCDATASection(xpathQuery, doc);
		cdataNode.setData(cdata);
	}
	
	public CDATASection getCDATASection(String xpathQuery, Document doc)
			throws Exception {

		CDATASection cdataNode = null;
		Node node = getNode(xpathQuery, doc);
		NodeList nodeList = node.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node child = nodeList.item(i);
			if (child instanceof CDATASection) {
				cdataNode = (CDATASection) child;
				
			}
		}
		return cdataNode;
	}
	
	public String queryForValue(String xPathQuery, String xml) throws Exception {

		InputSource inputSource = new InputSource(new StringReader(xml));
		XPath xPath = XPathFactory.newInstance().newXPath();
		XPathExpression xPathExpression = xPath.compile(xPathQuery);
		return xPathExpression.evaluate(inputSource);
		
	}
	
}
